/*
 * $Id: grep.c,v 1.19 2005-12-15 06:55:12 bacon Exp $
 */

#include <xp/utl/grep.h>
#include <xp/bas/memory.h>
#include <xp/bas/string.h>
#include <xp/bas/assert.h>

static int __get_line (xp_grep_t* grep);

xp_grep_t* xp_grep_open (xp_grep_t* grep)
{
	if (grep == XP_NULL) {
		grep = (xp_grep_t*) xp_malloc (xp_sizeof(xp_grep_t));
		if (grep == XP_NULL) return XP_NULL;
		grep->__dynamic = xp_true;
	}
	else grep->__dynamic = xp_false;

	if (xp_str_open(&grep->line, 0) == XP_NULL) {
		if (grep->__dynamic) xp_str_close (&grep->line);
		return XP_NULL;
	}

	grep->matcher = XP_NULL;
	grep->matcher_arg = XP_NULL;

	grep->input_func = XP_NULL;
	grep->input_arg = XP_NULL;
	grep->output_arg = XP_NULL;
	grep->output_arg = XP_NULL;

	grep->errnum = XP_GREP_ENOERR;
	grep->buf_cur = 0;
	grep->buf_len = 0;

	grep->__linenum = 0;
	grep->__atend = xp_false;

	return grep;
}

int xp_grep_close (xp_grep_t* grep)
{
	if (xp_grep_detach_matcher(grep) == -1) return -1;
	if (xp_grep_detach_input(grep) == -1) return -1;

	xp_str_close (&grep->line);
	if (grep->__dynamic) xp_free (grep);
	return 0;
}

int xp_grep_errnum (xp_grep_t* grep)
{
	return grep->errnum;
}

const xp_char_t* xp_grep_errstr (xp_grep_t* grep)
{
	static const xp_char_t* __errstr[] =
	{
		XP_TEXT("no error"),
		XP_TEXT("out of memory"),
		XP_TEXT("no input function attached"),
		XP_TEXT("input function returned an error"),
		XP_TEXT("input function failed to open"),
		XP_TEXT("input function failed to closed"),
		XP_TEXT("matcher returned an error"),
		XP_TEXT("unknown error")
	};

	return __errstr[
		(grep->errnum < 0 || grep->errnum >= xp_countof(__errstr))? 
		xp_countof(__errstr) - 1: grep->errnum];
}

int xp_grep_attach_input (xp_grep_t* grep, xp_grep_io_t input, void* arg)
{
	if (xp_grep_detach_input(grep) == -1) return -1;

	xp_assert (grep->input_func == XP_NULL);

	if (input(XP_GREP_IO_OPEN, arg, XP_NULL, 0) == -1) {
		grep->errnum = XP_GREP_EINPOP;
		return -1;
	}

	grep->input_func = input;
	grep->input_arg = arg;
	grep->__linenum = 0;
	grep->__atend = xp_false;

	return 0;
}

int xp_grep_detach_input (xp_grep_t* grep)
{
	if (grep->input_func != XP_NULL) {
		if (grep->input_func (
			XP_GREP_IO_CLOSE, grep->input_arg, XP_NULL, 0) == -1) {
			grep->errnum = XP_GREP_EINPCL;
			return -1;
		}

		grep->input_func = XP_NULL;
		grep->input_arg = XP_NULL;
	}
		
	return 0;
}

int xp_grep_attach_matcher (
	xp_grep_t* grep, xp_grep_matcher_t matcher, void* arg)
{
	if (xp_grep_detach_matcher(grep) == -1) return -1;

	xp_assert (grep->matcher == XP_NULL);

	grep->matcher = matcher;
	grep->matcher_arg = arg;

	return 0;
}

int xp_grep_detach_matcher (xp_grep_t* grep)
{
	if (grep->matcher != XP_NULL) {
		grep->matcher = XP_NULL;
		grep->matcher_arg = XP_NULL;
	}

	return 0;
}

int xp_grep_match (xp_grep_t* grep, xp_grep_match_t* match)
{
	int n;

	if (grep->input_func == XP_NULL) {
		grep->errnum = XP_GREP_ENOINF; 
		return -1;
	}

	if (grep->matcher == XP_NULL) {
		grep->errnum = XP_GREP_ENOMTR; 
		return -1;
	}	

	while (1) {
		n = __get_line(grep);
		if (n == -1) return -1;
		if (n == 0) break;
		grep->__linenum++;

		n = grep->matcher(grep->matcher_arg, 
			XP_STR_BUF(&grep->line), XP_STR_LEN(&grep->line));
		if (n == XP_GREP_CONTINUE) continue;
		else if (n == XP_GREP_RETURN_MATCH) {
			match->matched = xp_true;
			match->line = XP_STR_BUF(&grep->line);
			match->line_len = XP_STR_LEN(&grep->line);
			match->line_num = grep->__linenum;
			return 1;
		}
		else if (n == XP_GREP_RETURN_NOMATCH) {
			match->matched = xp_false;
			match->line = XP_STR_BUF(&grep->line);
			match->line_len = XP_STR_LEN(&grep->line);
			match->line_num = grep->__linenum;
			return 1;
		}
		else if (n == XP_GREP_RETURN_ERROR) {
			grep->errnum = XP_GREP_EMATCH;
			return -1;
		}

		grep->errnum = XP_GREP_EMATCH;
		return -1;
	}

	return 0;
}

static int __get_line (xp_grep_t* grep)
{
	xp_ssize_t n;

	xp_str_clear (&grep->line);

	if (grep->__atend) {
		grep->__atend = xp_false; 
		return 0;
	}

	while (1) {
		if (grep->buf_cur >= grep->buf_len) {
			n = grep->input_func (
				XP_GREP_IO_DATA, grep->input_arg,
				grep->buf, xp_countof(grep->buf));
			
			if (n == -1) {
				grep->errnum = XP_GREP_EINPUT;	
				return -1;
			}

			if (n == 0) { 
				grep->__atend = xp_true;
				if (XP_STR_LEN(&grep->line) == 0) return 0;
				return 1;
			}

			grep->buf_cur = 0;
			grep->buf_len = n;
		}

		xp_assert (grep->buf_cur < grep->buf_len);

		while (grep->buf_cur < grep->buf_len) {
			if (grep->buf[grep->buf_cur] == XP_CHAR('\n')) {
				grep->buf_cur++;
				goto exit_loop; /* NL */
			}
			else if (grep->buf[grep->buf_cur] == XP_CHAR('\r')) {
				grep->buf_cur++;
				if (grep->buf_cur + 1 < grep->buf_len && 
				    grep->buf[grep->buf_cur+1] == XP_CHAR('\n')) {
					/* CR + NL */
					grep->buf_cur++;
					goto exit_loop;
				}
				else goto exit_loop; /* CR */
			}

			if (xp_str_ccat(&grep->line, grep->buf[grep->buf_cur]) == -1) {
				grep->errnum = XP_GREP_ENOMEM;
				return -1;
			}

			grep->buf_cur++;
		}
	}

exit_loop:
	return 1;
}

int xp_grep_match_substr (
	void* arg, const xp_char_t* input_data, xp_size_t input_size)
{
	if (xp_strxstr (input_data, input_size, 
		(const xp_char_t*)arg) == XP_NULL) return XP_GREP_CONTINUE;
	return XP_GREP_RETURN_MATCH;
}
